/**
 * Vue Jsonp.
 * # Carry Your World #
 *
 * @author: LancerComet
 * @license: MIT
 */
import { flatten, formatParams, randomStr } from './utils';
const DEFAULT_TIMEOUT = 5000;
/**
 * Vue JSONP.
 */
// tslint:disable-next-line:variable-name
const VueJsonp = {
  install(V) {
    V.prototype.$jsonp = jsonp;
  }
};
/**
 * JSONP function.
 *
 * @param { string } url Target URL address.
 * @param { IJsonpParam } param Querying params object.
 * @param { number } timeout Timeout setting (ms).
 *
 * @example
 * jsonp('/url', {
 *   callbackQuery: ''
 *   callbackName: '',
 *   name: 'LancerComet',
 *   age: 26
 * }, 1000)
 */
function jsonp(url, param = {}, timeout) {
  if (typeof url !== 'string') {
    throw new Error('[Vue-jsonp] Type of param "url" is not string.');
  }
  if (typeof param !== 'object' || !param) {
    throw new Error('[Vue-jsonp] Invalid params, should be an object.');
  }
  timeout = typeof timeout === 'number'
    ? timeout
    : DEFAULT_TIMEOUT;
  return new Promise((resolve, reject) => {
    const callbackQuery = typeof param.callbackQuery === 'string'
      ? param.callbackQuery
      : 'callback';
    const callbackName = typeof param.callbackName === 'string'
      ? param.callbackName
      : 'jsonp_' + randomStr();
    param[callbackQuery] = callbackName;
    // Remove callbackQuery and callbackName.
    delete param.callbackQuery;
    delete param.callbackName;
    // Convert params to querying str.
    let queryStrs = [];
    Object.keys(param).forEach(queryKey => {
      queryStrs = queryStrs.concat(formatParams(queryKey, param[queryKey]));
    });
    const queryStr = flatten(queryStrs).join('&');
    
    const onError = () => {
      removeErrorListener();
      clearTimeout(timeoutTimer);
      reject({
        status: 400,
        statusText: 'Bad Request'
      });
    };
    const removeErrorListener = () => {
      paddingScript.removeEventListener('error', onError);
    };
    const removeScript = () => {
      document.body.removeChild(paddingScript);
      delete window[callbackName];
    };
    // Timeout timer.
    let timeoutTimer = null;
    // Setup timeout.
    if (timeout > -1) {
      timeoutTimer = setTimeout(() => {
        removeErrorListener();
        removeScript();
        reject({
          statusText: 'Request Timeout',
          status: 408
        });
      }, timeout);
    }
    // Create global function.
    window[callbackName] = (json) => {
      clearTimeout(timeoutTimer);
      removeErrorListener();
      removeScript();
      resolve(json);
    };
    // Create script element.
    const paddingScript = document.createElement('script');
    // Add error listener.
    paddingScript.addEventListener('error', onError);
    // Append to head element.
    paddingScript.src = url + (/\?/.test(url) ? '&' : '?') + queryStr;
    document.body.appendChild(paddingScript);
  });
}
export { VueJsonp, jsonp };
